/*
 * =====================================================================================
 *
 *       Filename:  cl_client_socket.cxx
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  03/24/2011 03:51:21 PM
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  Ken Chen (Ken), chen_kenz@emc.com
 *        Company:  EMC
 *
 * =====================================================================================
 */

#ifndef __GXX_EXPERIMENTAL_CXX0X__
#define __GXX_EXPERIMENTAL_CXX0X__
#endif

#include <cstring>
#include <cassert>
#include "cl_client_socket.hxx"
#include "cl_event_looper.hxx"
#include "cl_channel.hxx"
#include "cl_buffer.hxx"
#include "cl_id.hxx"
#include "cl_logger.hxx"

namespace neo
{
using namespace std;

const int CLClientSocket::MAX_RETRY_DELAY;
const int CLClientSocket::INIT_RETRY_DELAY;

CLClientSocket::CLClientSocket(CLEventLooper* looper,
		                           const string& hostServer, 
															 int port, 
															 CLSocketType type)
  : CLSocket(hostServer, port, type),
	m_eventLooper(looper),
  m_curState(SOCK_DISCONNECTED),
  m_retryDelay(INIT_RETRY_DELAY)
{
  setBlock(false);
}

CLClientSocket::~CLClientSocket() 
{
}

void CLClientSocket::connect(void) 
{
  m_eventLooper->runJob(bind(&CLClientSocket::connectInLooper, this));
}

void CLClientSocket::reconnect(void)
{
	setState(SOCK_DISCONNECTED);
  m_retryDelay = INIT_RETRY_DELAY;
  connectInLooper();
}

void CLClientSocket::connectInLooper(void)
{
  assert(isValid() && (m_curState == SOCK_DISCONNECTED));
  int ret = ::connect(m_socketfd, m_sockAddr->address(), m_sockAddr->size());
  int errcode = (ret == 0) ? 0 : errno;
  switch(errcode)
	{
    case 0:
		case EINPROGRESS:
		case EINTR:
		case EISCONN:
		  connecting();
			break;

		case EAGAIN:
		case EADDRINUSE:
		case EADDRNOTAVAIL:
		case ECONNREFUSED:
		case ENETUNREACH:
		  break;

		case EACCES:
		case EPERM:
		case EAFNOSUPPORT:
		case EALREADY:
		case EBADF:
		case EFAULT:
		case ENOTSOCK:
		  break;
	
		default:
			{
	      char errmsg[512];
		    int errcode = lastErrorCode();
	      CLLOG_E("NET", "CLClientSocket::connectInLooper %d %s",
				    errcode, strerror_r(errcode, errmsg, sizeof(errmsg)));
			}
      break;
	}
}

void CLClientSocket::setNewConnCallback(const CLClientSocket::NewConnCallback& cb) 
{
  m_newConnCallback = cb;
}

void CLClientSocket::setFailConnCallback(const CLClientSocket::FailConnCallback& cb)
{
  m_failConnCallback = cb;
}

void CLClientSocket::connecting(void)
{
  setState(SOCK_CONNECTING);
  m_connectChannel.reset(new CLChannel(m_socketfd)),
  m_connectChannel->enableWriting();
  m_connectChannel->setWriteCallback(bind(&CLClientSocket::handleWrite, this));
	m_connectChannel->setErrorCallback(bind(&CLClientSocket::handleError, this));
  m_eventLooper->addChannel(m_connectChannel.get());
}

void CLClientSocket::handleWrite(void)
{
  if(m_curState == SOCK_CONNECTING)  
	{
    removeChannel();
	  int errcode = lastErrorCode();
	  if(errcode)
		{
		  char errmsg[512];
		  CLLOG_W("NET", "CLClientSocket::handleWrite %d %s",
					errcode, strerror_r(errcode, errmsg, sizeof(errmsg))); 
			retry();
		}
		else
		{
		  setState(SOCK_CONNECTED);
			assert(m_newConnCallback);
		  m_newConnCallback(m_socketfd, move(CLSocketAddrUPtr(m_sockAddr->clone())));
		}
	}
}

void CLClientSocket::handleError(void)
{
  assert(m_curState == SOCK_CONNECTING); 
  removeChannel();
  int errcode = lastErrorCode();
	char errmsg[512];
  CLLOG_E("NET", "CLClientSocket::handleError %d %s",
			errcode, strerror_r(errcode, errmsg, sizeof(errmsg)));
  retry();
}

void CLClientSocket::removeChannel(void)
{
  m_connectChannel->disableAll();
  m_eventLooper->removeChannel(m_connectChannel.get());
  m_eventLooper->queueJob(bind(&CLClientSocket::resetChannel, this));
}

void CLClientSocket::resetChannel(void)
{
  m_connectChannel.reset();
}

void CLClientSocket::retry(void)
{
  setState(SOCK_DISCONNECTED);
  if(m_retryDelay >= MAX_RETRY_DELAY)
	{
		if(m_failConnCallback)
		{
		  m_failConnCallback();
		}
    CLLOG_E("NET", "CLClientSocket::retry, give up connecting to %s", 
				m_sockAddr->toString().c_str());
    return;
	}

  CLLOG_D("NET", "CLClientSocket::retry connect to %s", m_sockAddr->toString().c_str());
  m_eventLooper->runJobAfter(m_retryDelay, 
	                           bind(&CLClientSocket::connectInLooper, this));
  m_retryDelay = std::min(m_retryDelay * 2, MAX_RETRY_DELAY);
}

void CLClientSocket::setState(int state)
{
	m_curState = state;
}

} // neo
